// generic_led.c
//
// Purpose: Implements the highly generalized LED control logic.
// This module manages multiple LED instances of different types (monochrome, RGB),
// supporting various behaviors (on/off, blink, static color) through a
// data-driven configuration and a unified update task.
//
// System Thinking: A robust, reusable subsystem for managing all LEDs.
// Module Thinking: Encapsulates all LED hardware details, states, and behavior
//                  logic, exposing only a clean API.
// Computational Thinking: Implements state machines and timing logic for LED behaviors,
//                         driven by a single periodic task.

#include "generic_led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "xil_printf.h"
#include "hw_gpio_hal.h"  // For HAL_GPIO_WritePin, HAL_GPIO_SetDirection, etc.
#include <string.h>       // For strcmp, memset

// -----------------------------------------------------------------------------
// Internal Definitions and Constants
// -----------------------------------------------------------------------------
#define MAX_GENERIC_LEDS            10        // Maximum number of LEDs this module can manage
#define LED_TASK_UPDATE_INTERVAL_MS 20UL      // Base update rate for the LED control task

// FreeRTOS critical section macros for protecting shared resources
#define LED_ENTER_CRITICAL() taskENTER_CRITICAL()
#define LED_EXIT_CRITICAL()  taskEXIT_CRITICAL()

// Internal states for LED blinking
typedef enum {
    LED_BLINK_STATE_OFF,
    LED_BLINK_STATE_ON,
} LedBlinkInternalState_t;

// -----------------------------------------------------------------------------
// Internal LED Structure (private to this .c file)
// This structure holds the runtime state for each managed LED.
// -----------------------------------------------------------------------------
struct GenericLed {
    const char*                 name;           // Unique name
    LedType_t                   type;           // Type (Monochrome or RGB)
    bool                        active_low;     // Active low/high
    u32                         pins[3];        // Up to 3 pins (R, G, B or Mono)

    // Current desired state/mode (removed volatile)
    bool                        is_blinking;
    u32                         blink_interval_ms; // 0 if not blinking
    u32                         current_r, current_g, current_b; // Current desired color/state

    // Internal blinking state for task
    u32                         blink_counter;
    LedBlinkInternalState_t     blink_internal_state;
};

// Global pool to hold all registered LED instances
static GenericLed_t s_led_pool[MAX_GENERIC_LEDS];
static size_t       s_num_registered_leds = 0;

// -----------------------------------------------------------------------------
// Internal Helper Functions (static)
// -----------------------------------------------------------------------------

/**
 * @brief Sets the physical GPIO pin state for a monochrome LED.
 * @param pin The GPIO pin number.
 * @param active_low True if active low, false if active high.
 * @param state Desired logical state (LED_STATE_ON or LED_STATE_OFF).
 */
static void prv_set_monochrome_pin_state(u32 pin, bool active_low, LedState_t state)
{
    if (active_low) {
        HAL_GPIO_WritePin(pin, (state == LED_STATE_ON) ? 0 : 1);
    } else {
        HAL_GPIO_WritePin(pin, (state == LED_STATE_ON) ? 1 : 0);
    }
}

/**
 * @brief Sets the physical GPIO pin states for an RGB LED.
 * @param pins Array of R, G, B pins.
 * @param active_low True if active low, false if active high.
 * @param r Red component (0 or 1).
 * @param g Green component (0 or 1).
 * @param b Blue component (0 or 1).
 */
static void prv_set_rgb_pins_state(const u32 pins[3], bool active_low, u8 r, u8 g, u8 b)
{
    if (active_low) {
        HAL_GPIO_WritePin(pins[0], (r == 1) ? 0 : 1);
        HAL_GPIO_WritePin(pins[1], (g == 1) ? 0 : 1);
        HAL_GPIO_WritePin(pins[2], (b == 1) ? 0 : 1);
    } else {
        HAL_GPIO_WritePin(pins[0], (r == 1) ? 1 : 0);
        HAL_GPIO_WritePin(pins[1], (g == 1) ? 1 : 0);
        HAL_GPIO_WritePin(pins[2], (b == 1) ? 1 : 0);
    }
}

/**
 * @brief FreeRTOS task entry point for updating all registered LEDs.
 *        This task runs periodically and applies the current desired state
 *        and blink logic to the physical LED pins.
 * @param pvParameters Not used.
 *
 * Computational Thinking: A continuous loop implementing the LED update algorithm,
 *  handling blinking logic and applying states.
 * System Thinking: A critical concurrent component that drives the LED subsystem.
 */
static void generic_led_update_task_entry(void *pvParameters)
{
    (void) pvParameters;
    const TickType_t xTaskDelay = pdMS_TO_TICKS(LED_TASK_UPDATE_INTERVAL_MS);

    xil_printf("Generic LED Update Task Started with %u ms interval.\r\n", (unsigned int)LED_TASK_UPDATE_INTERVAL_MS);

    for (;;)
    {
        size_t current_num_leds;
        LED_ENTER_CRITICAL(); // Protect against s_num_registered_leds being modified by generic_led_init (if re-init)
        current_num_leds = s_num_registered_leds;
        LED_EXIT_CRITICAL();

        for (size_t i = 0; i < current_num_leds; i++)
        {
            // Access to s_led_pool[i] is safe here.
            // Individual led fields (led->is_blinking, led->current_r, etc.)
            // are modified by API functions which have their own critical sections.
            // Reading them here is fine without additional critical section per LED instance.
            GenericLed_t* led = &s_led_pool[i];

            if (led->is_blinking && led->blink_interval_ms > 0)
            {
                // Handle blinking logic
                led->blink_counter += LED_TASK_UPDATE_INTERVAL_MS;
                if (led->blink_counter >= led->blink_interval_ms)
                {
                    led->blink_internal_state = (led->blink_internal_state == LED_BLINK_STATE_ON) ? LED_BLINK_STATE_OFF : LED_BLINK_STATE_ON;
                    led->blink_counter = 0;
                }

                if (led->blink_internal_state == LED_BLINK_STATE_ON)
                {
                    if (led->type == LED_TYPE_MONOCHROME) {
                        prv_set_monochrome_pin_state(led->pins[0], led->active_low, (LedState_t)led->current_r);
                    } else if (led->type == LED_TYPE_RGB) {
                        prv_set_rgb_pins_state(led->pins, led->active_low, led->current_r, led->current_g, led->current_b);
                    }
                }
                else // LED_BLINK_STATE_OFF
                {
                    // Turn off for the "off" part of the blink cycle
                    if (led->type == LED_TYPE_MONOCHROME) {
                        prv_set_monochrome_pin_state(led->pins[0], led->active_low, LED_STATE_OFF);
                    } else if (led->type == LED_TYPE_RGB) {
                        prv_set_rgb_pins_state(led->pins, led->active_low, 0, 0, 0); // All off
                    }
                }
            }
            else // Not blinking, apply static state/color
            {
                if (led->type == LED_TYPE_MONOCHROME) {
                    prv_set_monochrome_pin_state(led->pins[0], led->active_low, (LedState_t)led->current_r);
                } else if (led->type == LED_TYPE_RGB) {
                    prv_set_rgb_pins_state(led->pins, led->active_low, led->current_r, led->current_g, led->current_b);
                }
            }
        }
        vTaskDelay(xTaskDelay);
    }
}

// -----------------------------------------------------------------------------
// API Functions Implementation (public)
// -----------------------------------------------------------------------------

int generic_led_init(const GenericLedConfig_t led_configs[], size_t num_leds)
{
    if (led_configs == NULL || num_leds == 0 || num_leds > MAX_GENERIC_LEDS) {
        xil_printf("generic_led_init: Invalid parameters or too many LEDs (%d max).\r\n", MAX_GENERIC_LEDS);
        return -1;
    }

    // Initialize global pool and register LEDs
    LED_ENTER_CRITICAL();
    s_num_registered_leds = 0; // Reset for re-initialization scenarios
    for (size_t i = 0; i < num_leds; i++)
    {
        GenericLed_t* led = &s_led_pool[s_num_registered_leds];
        memset(led, 0, sizeof(GenericLed_t)); // Clear the structure

        led->name = led_configs[i].name;
        led->type = led_configs[i].type;
        led->active_low = led_configs[i].active_low;

        // Configure GPIO direction as output and disable output enable
        HAL_GPIO_SetDirection(led_configs[i].pin_r_or_mono, 1);
        HAL_GPIO_SetOutputEnable(led_configs[i].pin_r_or_mono, 1);
        led->pins[0] = led_configs[i].pin_r_or_mono;

        if (led->type == LED_TYPE_RGB) {
            HAL_GPIO_SetDirection(led_configs[i].pin_g, 1);
            HAL_GPIO_SetOutputEnable(led_configs[i].pin_g, 1);
            led->pins[1] = led_configs[i].pin_g;

            HAL_GPIO_SetDirection(led_configs[i].pin_b, 1);
            HAL_GPIO_SetOutputEnable(led_configs[i].pin_b, 1);
            led->pins[2] = led_configs[i].pin_b;
        }

        // Set initial state (OFF)
        if (led->type == LED_TYPE_MONOCHROME) {
            prv_set_monochrome_pin_state(led->pins[0], led->active_low, LED_STATE_OFF);
            led->current_r = LED_STATE_OFF; // Use current_r for monochrome state
        } else if (led->type == LED_TYPE_RGB) {
            prv_set_rgb_pins_state(led->pins, led->active_low, 0, 0, 0);
            led->current_r = 0; led->current_g = 0; led->current_b = 0;
        }

        s_num_registered_leds++;
        xil_printf("  LED '%s' (Type %s) registered. Initial state OFF.\r\n",
                   led->name, (led->type == LED_TYPE_MONOCHROME) ? "MONO" : "RGB");
    }
    LED_EXIT_CRITICAL();

    // Create the FreeRTOS task for LED processing
    BaseType_t xReturned = xTaskCreate(
        generic_led_update_task_entry,
        "Generic_LED_Update",
        configMINIMAL_STACK_SIZE + 100, // Slightly more stack for flexibility
        NULL,
        tskIDLE_PRIORITY + 1,
        NULL
    );

    if (xReturned != pdPASS)
    {
        xil_printf("generic_led_init: Failed to create LED update task!\r\n");
        return -1;
    }

    return 0;
}

GenericLed_t* GenericLed_GetByName(const char* name)
{
    if (name == NULL) return NULL;

    LED_ENTER_CRITICAL();
    for (size_t i = 0; i < s_num_registered_leds; i++)
    {
        if (s_led_pool[i].name != NULL && strcmp(s_led_pool[i].name, name) == 0)
        {
            LED_EXIT_CRITICAL();
            return &s_led_pool[i];
        }
    }
    LED_EXIT_CRITICAL();
    return NULL;
}

int GenericLed_SetMonochromeState(GenericLed_t* led_instance, LedState_t state)
{
    if (led_instance == NULL || led_instance->type != LED_TYPE_MONOCHROME) {
        return -1;
    }
    LED_ENTER_CRITICAL();
    led_instance->is_blinking = false; // Stop blinking
    led_instance->blink_interval_ms = 0;
    led_instance->current_r = state; // Store desired state
    LED_EXIT_CRITICAL();
    return 0;
}

int GenericLed_SetMonochromeBlink(GenericLed_t* led_instance, u32 interval_ms)
{
    if (led_instance == NULL || led_instance->type != LED_TYPE_MONOCHROME) {
        return -1;
    }
    LED_ENTER_CRITICAL();
    led_instance->blink_interval_ms = interval_ms;
    led_instance->is_blinking = (interval_ms > 0);
    led_instance->blink_counter = 0;
    led_instance->blink_internal_state = LED_BLINK_STATE_ON; // Start ON
    led_instance->current_r = LED_STATE_ON; // Blink color/state is ON for monochrome
    LED_EXIT_CRITICAL();
    return 0;
}

int GenericLed_SetRGBColor(GenericLed_t* led_instance, u8 r, u8 g, u8 b)
{
    if (led_instance == NULL || led_instance->type != LED_TYPE_RGB) {
        return -1;
    }
    LED_ENTER_CRITICAL();
    led_instance->is_blinking = false; // Stop blinking
    led_instance->blink_interval_ms = 0;
    led_instance->current_r = r;
    led_instance->current_g = g;
    led_instance->current_b = b;
    LED_EXIT_CRITICAL();
    return 0;
}

int GenericLed_SetRGBBlink(GenericLed_t* led_instance, u32 interval_ms, u8 r, u8 g, u8 b)
{
    if (led_instance == NULL || led_instance->type != LED_TYPE_RGB) {
        return -1;
    }
    LED_ENTER_CRITICAL();
    led_instance->blink_interval_ms = interval_ms;
    led_instance->is_blinking = (interval_ms > 0);
    led_instance->blink_counter = 0;
    led_instance->blink_internal_state = LED_BLINK_STATE_ON; // Start ON
    led_instance->current_r = r;
    led_instance->current_g = g;
    led_instance->current_b = b;
    LED_EXIT_CRITICAL();
    return 0;
}

const char* GenericLed_GetName(const GenericLed_t* led_instance)
{
    if (led_instance == NULL) {
        return NULL;
    }
    // No critical section needed here as the name pointer is immutable after init.
    return led_instance->name;
}
